package com.golxen.framework.user.player;


import com.golxen.framework.AgentProtocol;
import com.golxen.framework.PBPacket.PBPacket;
import com.golxen.framework.executor.ThreadPlugin;
import com.golxen.framework.netty.session.game.GameServerLinkedSet;
import com.golxen.framework.log.LogMgr;
import com.golxen.framework.manager.TempMgr;
import com.golxen.framework.user.dao.UserBussiness;
import com.golxen.framework.user.dao.entity.UserInfo;
import com.golxen.utils.LogUtil;
import com.mysql.cj.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class GamePlayerMgr extends TempMgr {

    private static Map<Long, GamePlayer> playerMap = new ConcurrentHashMap<>();
    /**
     * 玩家PlayerMap 锁
     */
    private static final Map<Long, Object> PLAYER_LOCK_MAP = new ConcurrentHashMap<>();
    /**
     * 玩家保存使用的锁
     */
    private static final Map<Long, Object> PLAYER_SAVE_LOCK_MAP = new ConcurrentHashMap<>();

    private static ExecutorService executor = Executors.newFixedThreadPool(2);

    public static Object getPlayerLock(long userId) {
        if (!PLAYER_LOCK_MAP.containsKey(userId)) {
            synchronized (PLAYER_LOCK_MAP) {
                if (!PLAYER_LOCK_MAP.containsKey(userId)) {
                    PLAYER_LOCK_MAP.put(userId, new Object());
                }
            }
        }
        return PLAYER_LOCK_MAP.get(userId);
    }

    public static Object getPlayerSaveLock(long userId) {
        if (!PLAYER_SAVE_LOCK_MAP.containsKey(userId)) {
            synchronized (PLAYER_SAVE_LOCK_MAP) {
                if (!PLAYER_SAVE_LOCK_MAP.containsKey(userId)) {
                    PLAYER_SAVE_LOCK_MAP.put(userId, new Object());
                }
            }
        }
        return PLAYER_SAVE_LOCK_MAP.get(userId);
    }

    private static volatile boolean dbConnect = true;

    @Override
    public boolean stop() {
        return false;
    }

    @Override
    public boolean save() {
        return false;
    }

    @Override
    public boolean reloadData() throws Exception {
        return true;
    }

    @Override
    public boolean init() {
        playerMap = new ConcurrentHashMap<>();
        return true;
    }


    public static List<Long> getAllOnlinePlayerId() {
        List<Long> list = new ArrayList<>();
        if (playerMap != null) {
            for (Map.Entry<Long, GamePlayer> entry : playerMap.entrySet()) {
                if (entry.getValue().getState() == PlayerState.ONLINE) {
                    list.add(entry.getKey());
                }
            }
        }
        return list;
    }

    public static List<GamePlayer> getAllOnlinePlayer() {
        List<GamePlayer> list = new ArrayList<>();
        if (playerMap != null) {
            for (Map.Entry<Long, GamePlayer> entry : playerMap.entrySet()) {
                if (entry.getValue().getState() == PlayerState.ONLINE) {
                    list.add(entry.getValue());
                }
            }
        }
        return list;
    }

    public static Map<Long, GamePlayer> getCopyPlayerMap() {
        return new HashMap<>(playerMap);
    }

    public static Map<Long, GamePlayer> getPlayerMap() {
        return playerMap;
    }

    public static List<GamePlayer> getCopyPlayerList() {
        return new ArrayList<>(playerMap.values());
    }

    public static GamePlayer getOnlinePlayer(long userId) {
        return playerMap.get(userId);
    }

    public static GamePlayer getPlayer(long userId) {
        synchronized (getPlayerLock(userId)) {
            // 先获得玩家锁
            GamePlayer player = playerMap.get(userId);
            if (player != null) {
                return player;
            }

            player = loadPlayer(userId);
            if (player == null) {
                return null;
            }

            playerMap.put(userId, player);
            LogUtil.COMMON.info("user {} put in memory.", userId);
            return playerMap.get(userId);
        }
    }


    public static boolean timeSave(int saveIndex) {
        int saveUserDataThreadCount = ThreadPlugin.getCpu();
        List<Long> userIds = new ArrayList<>(playerMap.keySet());
        for (Long userId : userIds) {
            if (userId % saveUserDataThreadCount == saveIndex) {
                synchronized (getPlayerSaveLock(userId)) {
                    GamePlayer player = playerMap.get(userId);
                    if (player != null) {
                        player.save();
                    }
                }
            }
        }
        return true;
    }


    private static GamePlayer loadPlayer(long userId) {
        // 添加保存锁，保证保存和加载不会同时发生
        synchronized (getPlayerSaveLock(userId)) {
            UserInfo userInfo = UserBussiness.getUserInfoByUserId(userId);
            if (null == userInfo) {
                LogUtil.COMMON.error("not find userId = " + userId);
                return null;
            }
            if (StringUtils.isNullOrEmpty(userInfo.getNickName())) {
                LogUtil.COMMON.warn("not create role, userId = " + userId);
                return null;
            }
            GamePlayer player = new GamePlayer(userInfo);
            try {
                if (!player.loadDB()) {
                    return null;
                }
                player.afterLoadDB();
            } catch (Exception e) {
                LogUtil.COMMON.error("load player afterLoadDB error," + e.getMessage(), e);
                return null;
            }
            return player;
        }
    }

    public static void onLogout(GamePlayer player, boolean isKick) {

        if (player == null) {
            LogUtil.COMMON.debug("logout GamePlayer == null");
            return;
        }

        GamePlayer oldPlayer = getOnlinePlayer(player.getUserId());// 获取当前角色的GamePlayer
        if (oldPlayer == null) {
            LogUtil.COMMON.error("Disconnect player, userId = " + player.getUserId());
            return;
        }
        // 用户下线操作
        playerOffLine(oldPlayer);

        // 如果是剔除 才移出内存
        if (isKick) {
            removeFromMap(oldPlayer.getUserId());
        }

    }

    private static void playerOffLine(GamePlayer player) {
        player.setState(PlayerState.OFFLINE);
        LogMgr.addLogLogin(player.getUserId(), 1); // 下线记录  因为心跳检测的原因会连续记录两次 中间可能还会夹一个登录 有问题。。。

        // 在线时长：客户端实现
        // 登出
    }



    private static void removeFromMap(long userId) {
        // 先持有玩家锁，保证获得玩家和移出玩家不会同时发生
        synchronized (getPlayerLock(userId)) {

            GamePlayer player = getOnlinePlayer(userId);
            if (player != null && player.getState() == PlayerState.OFFLINE) {
                // 移出内存的时候，先获得玩家的save锁，保证和定时任务的save互斥

                if (!dbConnect) {
                    return;
                }
                player.save();
                if (!dbConnect) {
                    return;
                }
                playerMap.remove(userId);
                LogUtil.COMMON.info("user {} remove memory.", userId);
            }

        }
    }

    public static void sendPacket(GamePlayer player, PBPacket packet) {
        if (player != null && packet != null) {

            if (player.getState() < PlayerState.LOGIN) {
                LogUtil.COMMON.info("drop msg {} when {} state={}", packet.getCode(), player.getUserId(), player.getState());
                return;
            }

            sendPacket(player.getUserId(), packet);
        }
    }

    public static void sendPacket(long userId, PBPacket packet) {
        GameServerLinkedSet.send(userId, packet);
    }

    /**
     * 检测心跳
     */
    public static void heartBeat() {

        List<GamePlayer> gps = new ArrayList<GamePlayer>();
        int total = 0;
        int online = playerMap.size();
        for (GamePlayer player : playerMap.values()) {
            if (player != null) {
                // 每隔5秒检测一次
                long lostHeartBeatTime = System.currentTimeMillis() - player.getLastPingTime();
                if (lostHeartBeatTime > 60 * 1000) {
                    // 超过一分钟 心跳不在 开始记录丢失次数
                    player.setLoseHeartTimes(player.getLoseHeartTimes() + 1);
                }
                // 6 次丢失, 时间大概为 90 s , 设置为离线状态
                if (player.getLoseHeartTimes() > 6 && player.getState() >= PlayerState.LOGIN) {
                    // 只是设置用户下线
                    // 会恢复一次心跳
                    PBPacket castleReq = new PBPacket(AgentProtocol.A_PLAYER_OFFLINE);
                    castleReq.setPlayerId(player.getUserId());
                    GameServerLinkedSet.send(player.getUserId(), castleReq);
                }

                if (lostHeartBeatTime > 3 * 60 * 1000) {
                    // 丢失心跳3分钟 分钟 将玩家移出内存, 每次定时任务只移除6个(5s)
                    if (!dbConnect) {
                        continue;
                    }

                    total++;
                    if (gps.size() < 6) {
                        gps.add(player);
                    } else {
                        for (int i = 0; i < gps.size(); i++) {
                            if (player.getLastPingTime() < gps.get(i).getLastPingTime()) {
                                gps.set(i, player);
                                break;
                            }
                        }
                    }
                }
            }
        }

        LogUtil.COMMON.debug("heatbeat timeout kick player online {} total {} , tick {}", online, total, gps.size());

        if (gps.size() > 0) {
            for (GamePlayer player : gps) {
                LogUtil.COMMON.debug("kick player cause heartbeat time out 3 min  userId={}", player.getUserId());

                PBPacket castleReq = new PBPacket(AgentProtocol.A_KICK_OUT_PLAYER);
                castleReq.setPlayerId(player.getUserId());
                GameServerLinkedSet.send(player.getUserId(), castleReq);
            }
        }

    }

    public static void setDbConnect(boolean success) {
        dbConnect = success;
    }


}
